﻿using System;
using System.Data.Odbc;

namespace Centido.Core
{
    /// <summary>
    ///   A set of table delete operations.
    /// </summary>
    public static class TableDeleteOperations
    {
        /// <summary>
        ///   Deletes a row with the specified id.
        /// </summary>
        /// <param name="table">The target table.</param>
        /// <param name = "rowId">The id of the row.</param>
        /// <param name = "source">The target data source.</param>
        /// <returns>Returns true if a row with the specified id was found; otherwise, false.</returns>
        private static bool DeleteRowImplementation(Table table, Guid rowId, DataSource source)
        {
            if (source == DataSource.InternalTable)
            {
                return table.Domain.DbAccess.ExecuteNonQuery(
                    cmd =>
                    {
                        cmd.CommandText = string.Format("{{call {0}(?,?)}}", 
                            table.Domain.GetObjectFullName(Db.Procedures.DeleteTableRow));

                        cmd.Parameters.Add(table.Domain.DbAccess.CreateParameter("a_table_id",
                            OdbcType.Binary, table.GetRawId()));
                        cmd.Parameters.Add(table.Domain.DbAccess.CreateParameter("a_row_id",
                            OdbcType.Binary, rowId.ToByteArray()));
                    }) != 0;
            }

            return table.Domain.DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "delete from {0} where {1}=?",
                        table.FullName,
                        Db.Tables.UserTable.Col.Id);

                    cmd.Parameters.Add(table.Domain.DbAccess.CreateParameter("id",
                        OdbcType.Binary, rowId.ToByteArray()));
                }) != 0;
        }


        /// <summary>
        ///   Deletes a row with the specified id.
        /// </summary>
        /// <remarks>
        /// <para>Deleting one row may trigger deleting other rows if they have a reference on the row to be deleted
        /// and that reference has the CascadeDelete property set to true. If one reference in such a chain
        /// has the CascadeDelete property set to false then the whole transaction is cancelled
        /// and an exception is thrown.</para>
        /// <para>The values of such system columns as DeletedOn and DeletedBy
        /// a set after executing this function. These system columns are visible in the internal table only.</para>
        /// <para>The deleted row can be restored afterwards.</para>
        /// <para>The current user has to have the delete permission on the table in order to execute this function.</para>
        /// </remarks>
        ///<param name="table">The target table.</param>
        ///<param name = "rowId">The id of the specified row.</param>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no delete permission on the table.</exception>
        /// <exception cref="Centido.Core.DbOperationException">Thrown when there is no row with the specified id in the table.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when a reference in the chain of row deletion has the CascadeDelete property set to false.</exception>
        /// <returns>Returns true if a row with the specified id was found; otherwise, false.</returns>
        /// <example>
        ///   The following example shows the effect of cascade delete.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Creating another table
        ///             var anotherTable = domain.AddTable("another_table");
        /// 
        ///             //Creating a reference
        ///             var refColumn = table.AddColumn("ref_col", ColumnType.Reference)
        ///                 as RefColumn;
        /// 
        ///             refColumn.AddTarget(anotherTable).HasCascadeDelete = true;             
        /// 
        ///             //Granting all necessary permissions
        ///             table.GrantAll();
        ///             anotherTable.GrantAll();
        ///
        ///             //Inserting rows
        ///             Console.WriteLine("Inserting rows");
        /// 
        ///             var anotherTableId = anotherTable.InsertRow();
        ///             var id = table.InsertRow(refColumn.Value(anotherTableId));
        ///             
        ///             Console.WriteLine("Table - Row count = {0}", table.RowCount);
        ///             Console.WriteLine("Another table - Row count = {0}", anotherTable.RowCount);
        ///             
        ///             //Deleting the row in another table
        ///             Console.WriteLine("Deleting the row in the second table");
        ///             
        ///             anotherTable.DeleteRow(anotherTableId);
        ///             
        ///             Console.WriteLine("Table - Row count = {0}", table.RowCount);
        ///             Console.WriteLine("Another table - Row count = {0}", anotherTable.RowCount);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public static bool DeleteRow(this Table table, Guid rowId)
        {
            if (table == null)
                throw new ArgumentNullException("table");

            return DeleteRowImplementation(table, rowId, DataSource.UserView);
        }


        /// <summary>
        ///   Deletes a row with the specified id from the specified data source.
        /// </summary>
        /// <remarks>
        /// <para>When the data source is the user view then deleting one row may trigger deleting other rows,
        /// if they have a reference on the row to be deleted
        /// and that reference has the CascadeDelete property set to true. If one reference in such a chain
        /// has the CascadeDelete property set to false then the whole transaction is cancelled
        /// and an exception is thrown.
        /// When the data source is the internal table then the deletion is taken place as if
        /// the property CascadeDelete was set to true on all references.</para>
        /// <para>The values of such system columns as DeletedOn and DeletedBy
        /// a set after executing this function if the data source is the user view.
        /// These system columns are visible in the internal table only.</para>
        /// <para>The current user has to have the delete permission on the specified data source in order to execute this function.</para>
        /// </remarks>
        ///<param name="table">The target table.</param>
        ///<param name = "rowId">The id of the specified row.</param>
        /// <param name = "source">The specified data source.</param>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no delete permission on the specified data source.</exception>
        /// <exception cref="Centido.Core.DbOperationException">Thrown when there is no row with the specified id in the specified data source.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the data source is the user view and a reference in the chain of row deletion has the CascadeDelete property set to false.</exception>
        /// <returns>Returns true if a row with the specified id was found; otherwise, false.</returns>
        /// <example>
        ///   The following example shows the difference between deleting from the internal table and the table view.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Granting all necessary permissions
        ///             table.GrantAll();
        ///
        ///             //Inserting a row
        ///             Console.WriteLine("Inserting a row");
        /// 
        ///             var id = table.InsertRow();
        ///             
        ///             Console.WriteLine("Row count = {0}", table.RowCount);
        ///             Console.WriteLine("Internal row count = {0}", table.InternalRowCount);
        ///             
        ///             //Deleting the row from the table view
        ///             Console.WriteLine("Deleting the row from the table view");
        ///             
        ///             table.DeleteRow(id, DataSource.UserView);
        ///             
        ///             Console.WriteLine("Row count = {0}", table.RowCount);
        ///             Console.WriteLine("Internal row count = {0}", table.InternalRowCount);
        ///             
        ///             //Deleting the row from the internal table
        ///             Console.WriteLine("Deleting the row from the internal table");
        ///             
        ///             table.DeleteRow(id, DataSource.InternalTable);
        ///             
        ///             Console.WriteLine("Row count = {0}", table.RowCount);
        ///             Console.WriteLine("Internal row count = {0}", table.InternalRowCount);
        ///         }
        ///     } 
        /// </code>
        /// </example>
        public static bool DeleteRow(this Table table, Guid rowId, DataSource source)
        {
            return DeleteRowImplementation(table, rowId, source);
        }
    }
}
